#include <iostream>
#include <fstream>
#include <sstream>
#include <stdlib.h>
#include <new>
#include "wrapper.h"

#include "filesystem.h"
#include "concurrency.h"

using std::endl;
using std::cout;
using std::bad_alloc;
using std::fstream;

//****************************************************************************
// the main function
// this function starts the application according to the command line arguments
// supplied by the user.
//****************************************************************************
int main(int argc, char* argv[])
{
	CommandLineArguments *arguments = NULL;
	FileSystem *fileSystem = NULL;
	Concurrency *concurrency = NULL;
	try
	{
		arguments = new CommandLineArguments();
#ifdef WIN32	
		fileSystem = new WinFileSystem();
		concurrency = new WinConcurrency();
#else
		fileSystem = new OtherOsFileSystem();
		concurrency = new OtherOsConcurrency();
#endif
	}
	catch(bad_alloc *e)
	{
		cout<<"Memory allocation  failed"<< endl << e->what() <<endl;
		return terminateApp(arguments, fileSystem, concurrency);
	}
	
#ifdef WIN32
	//make the directory of the executables the current directory
	//look for the wrapper.exe in the current directory
	if(!fileSystem->IsFileExsits(WRAPPER_APP))
	{
		//wrapper.exe is not in the current dir: this means we are
		//running via the debugger. Change the current dir acordingly:
		string currDir = fileSystem->GetTheCurrentDirectory();
		fileSystem->NavigateUpFromCurrentDirectory(1);
		fileSystem->SetTheCurrentDirectory("Debug");
	}
#else
	cout<<"setting the current directory to a non windows platform is curently not implemented"<<endl;
	cout<<"application is for windows platform only.";
	return 0;
#endif

	if (!ParseCommandLineArguments(argc, argv, arguments))
	{
		cout<<"Parameters fail - Exit application."<<endl;
		return terminateApp(arguments, fileSystem, concurrency);
	}	
	else
	{
		cout<<"Parameters ok. Starting the application."<<endl;
	}

	//start running acording to the parmeter given:
	switch(arguments->m_Mode)
	{
		case BOTH:
		case PERMUTATIONS:
		{
			if(!fileSystem->MakeDirectory(OUTPUT_DIR))
			{
				cout<<"Failed to create the directory: "<< OUTPUT_DIR <<endl;
				cout<<"make sure the directory doesn't already exists"<<endl;
				return terminateApp(arguments, fileSystem, concurrency);
			}

			if(!fileSystem->MakeDirectory(permDirName))
			{
				cout<<"Failed to create the directory: "<< permDirName <<endl;
				cout<<"make sure the directory doesn't already exists"<<endl;
				return terminateApp(arguments, fileSystem, concurrency);
			}

#ifdef WIN32		
			string toExecute = PERMUTATIONS_APP + " " + arguments->m_ChainFileName  + " " + arguments->m_InputFileName + " " + permDirName;
#else
			string toExecute = "./" + PERMUTATIONS_APP + " " + arguments->m_ChainFileName  + " " + arguments->m_InputFileName + " " + permDirName ;
#endif
			int procID;
			concurrency->MakeProcess(toExecute, &procID);
			concurrency->WaitForProcess(procID);
			//system(toExecute.c_str()); //running PERMUTATIONS project on the given input chain
			
			if(arguments->m_Mode == PERMUTATIONS)
				break;
		}
		case GRAPHCOLORING:
		{	
			//collect all the files of type *.chain
			list<string> *fileNames = NULL;
			fileNames = fileSystem->ListAllFilesInDirectory(permDirName);
			if (fileNames == NULL)
			{
				cout<<"could not find files in the "<<permDirName<<" directory."<<endl;
				cout<<"make sure the directory exists."<<endl;
				return terminateApp(arguments, fileSystem, concurrency);
			}	
	
			//for each file execute the graphcoloring program			
			list<string>::iterator it = fileNames->begin();
			int i = 0;
			int *processes = NULL;
			try
			{
				processes = new int[arguments->m_NumOFChainsToProcessParallel];
			}
			catch(std::bad_alloc *e)
			{
				cout<<"memory allocation in wrapper failed"<<endl<<e->what()<<endl;
				return terminateApp(arguments, fileSystem, concurrency);
			}

			for(; it != fileNames->end(); it++)
			{
				//skip files that are not of type *.chain
				if(it->rfind(PERMUTATIONS_FILE_END) == string::npos )
					continue;

#ifdef WIN32			
				string chainToImproveFullName = permDirName + "\\" + (*it);
				string toExecute = GRAPHCOLORING_APP	+ " " + arguments->m_InputFileName + " " + chainToImproveFullName 
														+ " " + arguments->m_ChainFileName + " " + OUTPUT_DIR;
#else
				string chainToImproveFullName = permDirName + "//" + (*it);
				string toExecute = "./" + GRAPHCOLORING_APP + " " + arguments->m_InputFileName + " " + chainToImproveFullName 
															+ " " + arguments->m_ChainFileName + " " + OUTPUT_DIR;
#endif
				if (!concurrency->MakeProcess(toExecute, processes+i))
				{
					cout<<"failed to process chain in file: "<<*it<<endl;
					processes[i]=-1;
				}
				else
				{
					i++;
				}
				
				if(i == arguments->m_NumOFChainsToProcessParallel)
				{
					for(int j=0; j < arguments->m_NumOFChainsToProcessParallel; j++)
					{
						if(processes[j] > 0)//wait only for processes that were successfull.
							concurrency->WaitForProcess(processes[j]);
						processes[j]=-1;
					}
					i=0;
				}
			}
		}
		break;
	}

	cout<<"finished running the application."<<endl;
	cout<<"it's time to check the coloring results!"<<endl;

	return terminateApp(arguments, fileSystem, concurrency);
}

//****************************************************************************
// function defenitions
//****************************************************************************

//----------------------------------------------------------------------------
// Usage
// prints usage instructions for the user of the program
//----------------------------------------------------------------------------
void Usage()
{
	cout<<"To run the wrapper program:"<<endl<<endl;
	cout<<"wrapper.exe [chain_file_name] [input_file_name] [permutations|graphcoloring|both] [p=n]"<<endl<<endl;
	cout<<"First argument: mandatory."<<endl;
	cout<<"Specify the chain file name that contains the destination chain for the application."<<endl<<endl;
	cout<<"Second argument: mandatory."<<endl;
	cout<<"Specify the file name that contains the input variables for the application."<<endl<<endl;
	cout<<"Third  argument: mandatory."<<endl;
	cout<<"Specify if to run:"<<endl;
	cout<<"	permutations part only or,"<<endl;
	cout<<"	graphcoloring only or,"<<endl;
	cout<<"	both parts continuously."<<endl<<endl;;
	cout<<"Forth  argument: optional."<<endl;
	cout<<"if given, specify that n chains will be processed concurrently"<<endl;
	cout<<"	in the graph coloring part of the application."<<endl;
	cout<<"if not given, the chains will be processed one by one serially."<<endl<<endl;

	return;
}
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
// ParseCommandLineArguments
// process the command line arguments given and initialize a matching 
// CommandLineArguments structure
//
// @param argc
//		the number of command line arguments that were given
// @param argv
//		the array of strings containing the actual arguments
// @param argumentsOut
//		the CommandLineArguments structure that will contain the parsing result.
//		note: this is an output parameter, memory allocation for this structure
//		must be preformed before calling this function.
//
// @return bool
//		true if arguments were successfully parsed
//		false if not.
//----------------------------------------------------------------------------
bool ParseCommandLineArguments(int argc, char* argv[], CommandLineArguments *argumentsOut)
{
	if(argc < 2 || argc < 3 || argc < 4 || argc > 5)
	{
		cout<<"Error: Too few or too many arguments, please see usage."<<endl<<endl;
		Usage();
		return false;
	}
	
	FileSystem *fileSystem = NULL;
	try
	{
#ifdef WIN32
		fileSystem = new WinFileSystem();
#else
		fileSystem = new OtherOsFileSystem();
#endif
	}
	catch(std::bad_alloc *e)
	{
		e = NULL;
		return false;
	}

	argumentsOut->m_ChainFileName = argv[1];
	if(!fileSystem->IsFileExsits(argv[1]))
	{
		cout<<"Error: could not open the chain file: " << argumentsOut->m_ChainFileName <<endl;
		cout<<"make sure the file name is spelled correctly"<<endl;
		cout<<"and that the file is in the same directory as the application executable."<<endl;
		delete fileSystem;
		return false;
	}

	argumentsOut->m_InputFileName = argv[2];
	if(!fileSystem->IsFileExsits(argv[2]))
	{
		cout<<"Error: could not open the configuration file: " << argumentsOut->m_InputFileName <<endl;
		cout<<"make sure the file name is spelled correctly"<<endl;
		cout<<"and that the file is in the same directory as the application executables."<<endl;
		delete fileSystem;
		return false;
	}
	delete fileSystem;
	fileSystem = NULL;

	string operationModeStr = argv[3];
	if(operationModeStr.compare("permutations") == 0)
	{
		argumentsOut->m_Mode = PERMUTATIONS;
	}
	else if(operationModeStr.compare("graphcoloring") == 0)
	{
		argumentsOut->m_Mode = GRAPHCOLORING;
	}
	else if(operationModeStr.compare("both") == 0)
	{
		argumentsOut->m_Mode = BOTH;
	}
	else
	{
		cout<<"Error: Unknown running mode, please see usage:"<<endl<<endl;
		Usage();
		return false;
	}

	argumentsOut->m_NumOFChainsToProcessParallel = 1;
	if(argc==5)
	{
		string param = argv[4];	
		int pos = param.find("p=");
		if (pos != string::npos)
		{
			//extract the number of chains to process in parallel
			string numberStr = param.substr(pos+2, string::npos);
			argumentsOut->m_NumOFChainsToProcessParallel = atoi(numberStr.c_str());
			if (argumentsOut->m_NumOFChainsToProcessParallel > MAX_THREADS )
			{
				cout<<"Error: too many threads were requested."<<endl<<endl;
				cout<<"use "<<MAX_THREADS<<" at most."<<endl;
				return false;
			}
		}
		else
		{
			cout<<"Error: Unknown last argument, please see usage:"<<endl<<endl;
			Usage();
			return false;
		}
	}

	return true;
}
//----------------------------------------------------------------------------

//----------------------------------------------------------------------------
// terminateApp
// frees memory and do other related end of application activities.
//
// @param arguments
//		the arguments of the application to delete
// @param fileSystem
//		the fileSystem of the application to delete
// @param concurrency
//		the concurrency of the application to delete
// note: default values for all parameters: NULL.
//
// @return int
//		the return value for this application execution
//----------------------------------------------------------------------------
int terminateApp(CommandLineArguments *arguments, FileSystem *fileSystem, Concurrency *concurrency)
{
	delete arguments;
	delete fileSystem;
	delete concurrency;
	
	cout<<"terminating application................."<<endl;
	return 0;
}